/* Sokoban
 * HIT3243
 * Authors: Mike Blackney, Ben Ingarfield, Kurniawan
 */

#include "level.h"
#include "ai.h"
#include "registrar.h"
#include "camera.h"
#include "xfileentity.h"
#include "dxf.h"

#define MAX_LEV_HEIGHT 16

#define TILE_WALL   'X'
#define TILE_FLOOR  '.'
#define TILE_PLAYER 'p'
#define TILE_CRATE  'o'
#define TILE_NULL   ' '
#define TILE_TARGET 't'
#define TILE_LIGHT  'L'

// This is the level definition array.
// It contains the level maps.  Levels can be as wide as you want, but may
// only be MAX_LEV_HEIGHT squares y-wise.  If a level is less than 16 squares high, the
// last+1 Y string must be NULL.
const char *const level_def[][MAX_LEV_HEIGHT] = {
// Level 1:
	{ 
	"        XXXXX",
	" XXXX   X.ttX",
	" X..XXXXX..tX",
	" X..o.......X",
	"XX.XX....X.XX",
	"X...X.X..X.X ",
	"X..o.oXX...X ",
	"X...X..X..XX ",
	"XXXXX.....X  ",
	"    X...p.X  ",
	"    XXXXXXX  ",
	NULL
	}, 

// Level 2:
	{
	"         XXLXX",
	"XXXXXXXXXX...X",
	"X...XX.......X",
	"X......tXX...X",
	"X...XXXXXXX.XX",
	"XX.XX     X.X ",
	" XoX XXXXXXoXX",
	" X.XXX...XX..X",
	" X.....o.p..tX",
	" Xt.XX...XXXXX",
	" XXXXXXXXX    ",
	NULL
	},

// Add more levels here:

// Level 3 (S1):
	{
	"  XXXX  ",
	" XXLXXX ",
	" X....XX",
	"XXtXXo.X",
	"X.tto.pX",
	"X..Xo..X",
	"X......X",
	"XXXXXXXX",
	"  XXXX  ",
	NULL
	},

// Level 4 (S18):
	{
	"  XXXX  ",
	"XXXXLXXX",
	"Xt...t.X",
	"X.X.X..X",
	"Xpo..otX",
	"XXXXX.oX",
	"    X..X",
	"    XXXX",
	"     XX ",
	NULL
	},

// Level 5 (S19):
	{
	" XX     ",
	"XXLX    ",
	"X..X    ",
	"X..XXXXX",
	"X.tto..X",
	"XXo....X",
	" X.XoXXX",
	" Xt.pX  ",
	" XXXXX  ",
	"  XXX   ",
	NULL
	},

// Level 6 (S20):
	{
	"  XXX   ",
	" XXLXX  ",
	" X.p.XXX",
	"XX.t...X",
	"Xt.oto.X",
	"XXoX.XXX",
	" X...X  ",
	" XXXXX  ",
	"  XXX   ",
	NULL
	},

// Level 7 (S22):
	{
	"  XX    ",
	" XXLX   ",
	"XX..XXXX",
	"Xtto..tX",
	"X.Xo.o.X",
	"Xp..X..X",
	"XXXXX..X",
	"    XXXX",
	"     XX ",
	NULL
	},


// Then NULL terminated to let the caller know
// when we're at the end of the list:
	{NULL}
};

// The camera for the 3d viewport:
CCamera *gCamera=NULL;
// Temp matrix for world rotations:
D3DXVECTOR3 gModelRotations(0,0,0);

// Length of a demo level:
#define DEMO_LENGTH 4.0f
// Time you pause for when going to new level:
#define LEVEL_PAUSE 3.0f
// Points you get per tile:
#define TILE_LAND_SCORE 25

// Number of tiles in a level:
#define TILES_PER_GAME_LEVEL 28

// Player/Monster start positions:
#define PLAYER_START_X 0
#define PLAYER_START_Y 0
#define PLAYER_START_Z 0

#define BONUS_SCORE 1200
#define PAUSE_POSITION D3DXVECTOR2(400,525)

// Monsters start floating
#define MONSTER_START_X static_cast<float>(floor(fmod(fTime+0,2)))
#define MONSTER_START_Y static_cast<float>(floor(fmod(fTime+1,2)))
#define MONSTER_START_Z -5

// World translation ratios for the 3d models:
#define NINJA_WORLD  D3DXVECTOR3(-150,-150,-150)
#define WALL_WORLD   D3DXVECTOR3(-150,-150,-150)
#define CRATE_WORLD  D3DXVECTOR3(-150,-150,-150)


Level::Level( ) 
:	tiles(NULL),
	bResetTiming(true),
	bPause(false),
	bNewLevel(false),
	iLevelWidth(0),
	iLevelHeight(0),
	fPauseTimer(0.0f),
	player(NULL),
	pd3dDevice(NULL){

	// go through the level data array and work out the number of
	// levels we have been given:
	for (iNumLevels= 0; level_def[iNumLevels][0] != NULL; ++iNumLevels)
		/* NOTHING */;

	// Make sure we have at least one level:
	assert(iNumLevels && "I'm sorry, but you need to make some levels in LEVEL.CPP");

}

Level::~Level( ) {

}

Level &Level::Instance( ) {
	static Level level;
	return level;
}

HRESULT Level::Load( ) {

	HRESULT hr;

	// Load a font for the scores:
	V_RETURN(font.Load(L"Tahoma", 24, FW_NORMAL));

	// Load the sound files:
//	V_RETURN( sndCollide.Load(L"s_collide.wav") );
//	V_RETURN( sndDemo.Load(L"s_demo.wav") );
//	V_RETURN( sndEndLevel.Load(L"s_endlevel.wav") );
//	V_RETURN( sndFall.Load(L"s_fall.wav") );
//	V_RETURN( sndFloat.Load(L"s_float.wav") );
//	V_RETURN( sndJump.Load(L"s_jump.wav") );
//	V_RETURN( sndEnemyJump.Load(L"s_enemyjump.wav") );

	// Create a camera class
	gCamera=new CCamera;

	// Clear the level scoring counters:
	Registrar::iNumTurns  = 0;
	Registrar::iNumPushes = 0;
	Registrar::fTime = 0;

	// Clear the level undo list:
	undoList.clear( );

	return S_OK;
}

#define DELETE_AND_NULL(x) {delete x;x=NULL;}

void Level::Unload( ) {

	ClearTiles( );
	ClearPawns( );
	crates.clear( );

	font.Unload( );

/*
	sndCollide.Unload( );
	sndDemo.Unload( );
	sndEndLevel.Unload( );
	sndFall.Unload( );
	sndFloat.Unload( );
	sndJump.Unload( );
	sndEnemyJump.Unload( );
	*/

	DELETE_AND_NULL(gCamera);

	DELETE_AND_NULL( Registrar::mdl_wall );
	DELETE_AND_NULL( Registrar::mdl_ninja );
	DELETE_AND_NULL( Registrar::mdl_crate );
	DELETE_AND_NULL( Registrar::mdl_litwall );
	DELETE_AND_NULL( Registrar::mdl_floor );
	DELETE_AND_NULL( Registrar::mdl_targetfloor );
	Registrar::bHasLoaded = false;
	
}

void Level::PlaySound( int sound ) {
	switch (sound) {
		/*
		case SND_COLLIDE :
			sndCollide.Play( );
			break;
		case SND_DEMO :
			sndDemo.Play( );
			break;
		case SND_ENDLEVEL :
			sndEndLevel.Play( );
			break;
		case SND_FALL :
			sndFall.Play( );
			break;
		case SND_FLOAT :
			sndFloat.Play( );
			break;
		case SND_JUMP :
			sndJump.Play( );
			break;
		case SND_ENEMYJUMP :
			sndEnemyJump.Play( );
			break;*/
		default:
			//assert(false);
			break;
	}
}


void Level::ClearTiles( ) {
// Clears the tiles list if it's been allocated:
	if ( tiles ) {
		for (int x = 0; x < iLevelWidth; ++x) 
			delete [] tiles[x];

		delete [] tiles;
		tiles = NULL;
	}

	iLevelWidth = 0;
}

void Level::ClearPawns( ) {
// Deletes all Pawns on the level and empties the list:
	pawns.Clear( );

	// Player is gone, snake is gone:
	this->player = NULL;
	//bSnake = false;
}


HRESULT Level::PrepareGame( int level ) {

	bResetTiming = true;
	bNewLevel = false;
	Registrar::iLastLevel = iCurrLevel;
	mLevelState = LS_Begin;

	ClearTiles( );

	// Work out the level width and height:
	for (iLevelHeight = 0, iLevelWidth = 0; 
		(level_def[iCurrLevel][iLevelHeight] != NULL) && (iLevelHeight < MAX_LEV_HEIGHT) ; ++iLevelHeight) {

		int iWidth;
		for (iWidth = 0; level_def[iCurrLevel][iLevelHeight][iWidth] != NULL; ++iWidth)
			/* NOTHING */;

		if (iWidth > iLevelWidth) iLevelWidth = iWidth;
	}

	return S_OK;
}

void Level::PreparePlayer( ) {
	// Makes a player pawn for us to watch or play with.

	// ---- NORMAL GAME
	// Make a player pawn:
	player = new Pawn( TYPE_PLAYER,
					new PlayerAi(0), // Player AI
					pd3dDevice,
					Registrar::mdl_ninja, NINJA_WORLD,
					PLAYER_START_X,PLAYER_START_Y,PLAYER_START_Z // Begins above which tile?
					);
	// And add to the pawn list:
	pawns.Add( *player );
}

void Level::PrepareTiles( ) {
	// This method prepares the tile-based parts of the map.
	// We need the player and crates to already have been initialised
	// by the time we get here, because we do the positioning of them
	// based on the level info structure (we don't want to bother
	// recursing over that structure too often.)

	// Sanity check:
	assert(tiles==NULL);

	// Allocate and clear the tiles:
	int x, y;
	tiles = new TileInfo* [ iLevelWidth ];

	// Clear the targets counter:
	iNumTargets = 0;

	for (x = 0; x < iLevelWidth; ++x) {

		tiles[x] = new TileInfo[ iLevelHeight ];

		for (y = 0; y < iLevelHeight; ++y) {

			tiles[x][y].state = 0;

			// BUILD THE LEVEL WALLS AND FLOOR:

			// If the tile is a wall, place it:
			if (   level_def[iCurrLevel][y][x] == TILE_WALL
				|| level_def[iCurrLevel][y][x] == TILE_LIGHT ) {

					Pawn *temp_pawn = new Pawn( 
						TYPE_WALL,
						NULL, // No AI function
						pd3dDevice,
						Registrar::mdl_wall, WALL_WORLD,
						static_cast<float> (x),
						static_cast<float> (y),
						static_cast<float> (0) );

					pawns.Add( *temp_pawn );
			}
			// Otherwise, unless it's NULL it's a floor, so place it:
			else if ( level_def[iCurrLevel][y][x] != TILE_NULL ) {

				// If it's a target floor, place special:
				if ( level_def[iCurrLevel][y][x] == TILE_TARGET ) {
					Pawn *temp_pawn = new Pawn( 
						TYPE_WALL,
						NULL, // No AI function
						pd3dDevice,
						Registrar::mdl_targetfloor, WALL_WORLD,
						static_cast<float> (x),
						static_cast<float> (y),
						static_cast<float> (1) );
					pawns.Add( *temp_pawn );
				}
				// else it's a normal floor:
				else {
					Pawn *temp_pawn = new Pawn( 
						TYPE_WALL,
						NULL, // No AI function
						pd3dDevice,
						Registrar::mdl_floor, WALL_WORLD,
						static_cast<float> (x),
						static_cast<float> (y),
						static_cast<float> (1) );
					pawns.Add( *temp_pawn );
				}
			}

			// If the player is here, place him:
			if ( level_def[iCurrLevel][y][x] == TILE_PLAYER ) {
				assert(player);
				player->x= static_cast<float> (x);
				player->y = static_cast<float> (y);
				player->z = static_cast<float> (0);
				PlayerAi::SetX( static_cast<float> (x) );
				PlayerAi::SetY( static_cast<float> (y) );
				PlayerAi::SetZ( static_cast<float> (0) );
				player->GetAi()->Reset(player);
				// Register the initial position:
				RegisterMovement( player );
			}

			// If there's a crate here, place it:
			if ( level_def[iCurrLevel][y][x] == TILE_CRATE ) {
					Pawn *temp_pawn = new Pawn( 
						TYPE_CRATE,
						NULL, // No AI function
						pd3dDevice,
						Registrar::mdl_crate, CRATE_WORLD,
						static_cast<float> (x),
						static_cast<float> (y),
						static_cast<float> (0) );

					pawns.Add( *temp_pawn );
					// Add the crate to the crates list:
					crates.push_back( temp_pawn );

					// And register the initial position:
					RegisterMovement( temp_pawn );
			}

			// If there's a target location here, add one to the target loc list:
			if ( level_def[iCurrLevel][y][x] == TILE_TARGET )
				++iNumTargets;


		}
	}
}

void Level::Update( double fTime, float fElapsedTime ) {

	// What this function does depends on the level state:
	switch (mLevelState) {

		case LS_Begin: {
			// Go to the next level state -- begin already!
			mLevelState = LS_PregamePause;
			break;
		}
		case LS_PregamePause: {
			// Pause until user hits space:
			if (dxf::DXFCheckKeyboard(VK_SPACE) == dxf::BUTTON_DOWN) 
				mLevelState = LS_PregameZoom;
			// Time is zero until we're in the game:
			Registrar::fTime = 0;
			break;
		}
		case LS_PregameZoom: {
			// If we've finished zooming, go to the next level state:
			if ( MoveCamera( fElapsedTime ) )
				mLevelState = LS_Game;
			// Time is zero until we're in the game:
			Registrar::fTime = 0;
			break;
		}

		case LS_Game: {

			// Wait until we've loaded the level:
			if (!Registrar::bHasLoaded)
				return;

			// Move the camera:
			MoveCamera( fElapsedTime );
		
			// If we need to make a new level, do it now:
			if (bNewLevel) {
				// (so long as we're not paused) :)
				if (!bPause) { 
	
					//Increase the level:
					if (!level_def[++iCurrLevel][0])
						iCurrLevel = 0;
	
					// Go to the next level state:
					mLevelState = LS_PostwinZoom;

				}
			}
	
			if (bResetTiming == true) {
				fLevelStartTime = fTime;
				bResetTiming = false;
			}
	
			// Only update the pawns if we're not pausing:
			if (false == bPause) {
				pawns.Update( fTime, fElapsedTime );
			}
			// Otherwise, if we've paused long enough, reset the level:
			else if ( (fPauseTimer -= fElapsedTime) < 0 ) {
				bPause = false;
				return;
			}

			break;
		}

		case LS_PostwinZoom: {
			// Zoom in on the hero...
			// If we've zoomed enough, go to the next state:
			if ( MoveCamera( fElapsedTime ) ) 
				mLevelState = LS_End;

			break;

		}

		case LS_End:
			/*Fall through:*/
		default: {
			// If game has ended, or invalid state, go to High Score screen:
			Registrar::bEnteringScore = true;
			dxf::DXFChangeState(Registrar::kHiScore);
		}

	}
	
}
	

HRESULT Level::OnCreateDevice( IDirect3DDevice9* device, const D3DSURFACE_DESC* ) {
	// This method is meant for adding things to the screen and
	// doing other visual initialisation.  We also use it to load
	// our 3d models and creating the mandatory pawns.  We do that
	// here because we need a reference to a D3Ddevice to load them.

	assert(device);

	HRESULT hr = S_OK;

	pd3dDevice = device;

	// Position the camera:
	CameraPos = D3DXVECTOR3(-1500.0f,2500.0f,-750.0f);
	DesiredCameraPos = CameraPos;
	//D3DXVECTOR3 look=modelCentre-pos;
	CameraLook = D3DXVECTOR3(0.5f,-2,0);
	DesiredCameraLook = CameraLook;
	D3DXVec3Normalize(&CameraNormal,&CameraLook);
	gCamera->SetView(CameraPos,CameraNormal);

	gModelRotations=D3DXVECTOR3(0,0,0);
	D3DXMATRIX world_matrix;
	D3DXMATRIX temp_matrix;

	//--  Load the models:
	// Hero:
	Registrar::mdl_ninja = new CXFileEntity;
	//V_RETURN( Registrar::mdl_ninja->LoadXFile(pd3dDevice,L"Q-bert-3d_07.X") );
	V_RETURN( Registrar::mdl_ninja->LoadXFile(pd3dDevice,L"NinjaSokoban.X") );
	// Ninja model is really freaking huge:
	D3DXMatrixIdentity( &world_matrix );
	D3DXMatrixScaling(&temp_matrix, 75.0f, 75.0f, 75.0f );
	D3DXMatrixMultiply( &world_matrix, &temp_matrix, &world_matrix );
	D3DXMatrixTranslation(&temp_matrix, -1.0f, 0.0f, 0.8f );
	D3DXMatrixMultiply( &world_matrix, &temp_matrix, &world_matrix );
	Registrar::mdl_ninja->SetWorldTransform( &world_matrix );

	// Walls:
	Registrar::mdl_wall = new CXFileEntity;
	V_RETURN( Registrar::mdl_wall->LoadXFile(pd3dDevice,L"S_Wall.X") );
	// Lit Walls:
	Registrar::mdl_litwall = new CXFileEntity;
	V_RETURN( Registrar::mdl_litwall->LoadXFile(pd3dDevice,L"S_LitWall.X") );
	// Floors:
	Registrar::mdl_floor = new CXFileEntity;
	V_RETURN( Registrar::mdl_floor->LoadXFile(pd3dDevice,L"S_Floor.X") );
	Registrar::mdl_targetfloor = new CXFileEntity;
	V_RETURN( Registrar::mdl_targetfloor->LoadXFile(pd3dDevice,L"S_TargetFloor.X") );

	// Crates:
	Registrar::mdl_crate = new CXFileEntity;
	V_RETURN( Registrar::mdl_crate->LoadXFile(pd3dDevice,L"S_Crate.X") );

	Registrar::bHasLoaded = true;
	

	// Always need player first!
	PreparePlayer( );

	// Always need tiles:
	PrepareTiles( );

	return hr;

}

#define EPSILON 300.0f
#define FLOAT_EQ(x,v) (((v - EPSILON) < x) && (x <( v + EPSILON)))

bool Level::MoveCamera( float fElapsedTime ) {
	// Moves the camera per Level::update( ).

	// How fast does the camera turn?
	double cameraTurnSpeed = 1.5f;

	// Whether we follow the player or what depends on
	// the difficulty:
	switch (Registrar::iDifficulty) {
		case 0: 
			// Easy mode - camera stays put.
			DesiredCameraPos = D3DXVECTOR3( -1500.0f, 2500.0f, -750.0f );

			/*DesiredCameraPos = D3DXVECTOR3( 
								player->y * -150 - 1100.0f,
								player->z * -150 + 2500.0f,
								player->x * -150 );*/

			DesiredCameraLook = D3DXVECTOR3( 0.5f, -2.0f, 0.0f );
			/*
			DesiredCameraLook = 
				D3DXVECTOR3( player->y * -150, player->z * -150, player->x * -150 )
				- CameraPos;
			*/
			break;
		case 1:
			// Medium mode - camera is a little zoomed on the player:
			DesiredCameraPos = D3DXVECTOR3( 
								player->y * -150 -  550.0f,
								player->z * -150 + 1250.0f,
								player->x * -150 );

			DesiredCameraLook = D3DXVECTOR3( 0.5f, -2.0f, 0.0f );
			/*
			DesiredCameraLook = 
				D3DXVECTOR3( player->y * -150, player->z * -150, player->x * -150 )
				- CameraPos;
				*/
			break;
		case 2:
			// Hard mode - camera is FPS:

			// In hard mode, player AI sets camera position:
			/*
			DesiredCameraPos = D3DXVECTOR3( 
								player->y * -150 -  550.0f,
								player->z * -150 + 1250.0f,
								player->x * -150 );
			*/
			DesiredCameraLook = 
				D3DXVECTOR3( player->y * -150, CameraPos.y - 15, player->x * -150 )
				- CameraPos;

			cameraTurnSpeed = 6.0f;
			break;
		default:
			// No known mode.  PROBLEM!
			assert(false);
			break;

	}

	// Here's a special case for if we're zooming on the front
	// of the player at the end of a level:
	if (mLevelState == LS_PostwinZoom) {

		// Place the camera right in front of the player and
		// facing him?

		// We'll just set this to the default of 'zoomed out plenty':
			DesiredCameraPos = D3DXVECTOR3( -1500.0f, 2500.0f, -750.0f );
			DesiredCameraLook = D3DXVECTOR3( 0.5f, -2.0f, 0.0f );
	}

	// -- Update the camera to move towards the desired position:

	// We need to keep track of the camera movement with acceleration
	// variables so it will be more like a real camera:
	static D3DXVECTOR3 CameraAcceleration(0,0,0);
	static D3DXVECTOR3 CameraTorque(0,0,0);

	D3DXVECTOR3 DesiredAcceleration;
	D3DXVECTOR3 DesiredTorque;

	DesiredAcceleration = (DesiredCameraPos - CameraPos) * fElapsedTime * 1.5f;
	CameraAcceleration += (DesiredAcceleration - CameraAcceleration) / 2;

	DesiredTorque = (DesiredCameraLook - CameraLook) * fElapsedTime * cameraTurnSpeed;
	CameraTorque += (DesiredTorque - CameraTorque) / 2;

	// Move the camera:
	CameraPos  += CameraAcceleration;
	CameraLook += CameraTorque;
	D3DXVec3Normalize(&CameraNormal,&CameraLook);

	// If the camera pos equals the desired camera pos,
	// return true else return false:
	return (   FLOAT_EQ(CameraPos.x, DesiredCameraPos.x) 
			&& FLOAT_EQ(CameraPos.y, DesiredCameraPos.y) 
			&& FLOAT_EQ(CameraPos.z, DesiredCameraPos.z) );
}

void Level::UpdateCameraHardware( ) {
	// Updates the camera per render3d() call.
	gCamera->SetView(CameraPos,CameraNormal);
}

HRESULT Level::OnResetDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc) {

	HRESULT hr = S_OK;

	// Setup basic render state
	pd3dDevice->SetRenderState( D3DRS_LIGHTING,         TRUE );
	pd3dDevice->SetRenderState( D3DRS_DITHERENABLE,     TRUE );
	pd3dDevice->SetRenderState( D3DRS_SPECULARENABLE,	FALSE );
	pd3dDevice->SetRenderState( D3DRS_ZENABLE,          TRUE );
	pd3dDevice->SetRenderState( D3DRS_CULLMODE,         D3DCULL_CCW );
	pd3dDevice->SetRenderState( D3DRS_AMBIENT,          0x999999 );
	pd3dDevice->SetRenderState( D3DRS_NORMALIZENORMALS, TRUE );

	// Setup states effecting texture rendering:
	pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );
	pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
	pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_CURRENT /*D3DTA_DIFFUSE*/ );
	pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );
	pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
	pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
	pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
	pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );

	// Set the projection matrix
	D3DXMATRIX matProj;
	float fAspect=(float)DXUTGetBackBufferSurfaceDesc()->Width/DXUTGetBackBufferSurfaceDesc()->Height;
	D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 1.0f, 5000.0f );
	pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

	// Create a light
	D3DLIGHT9 light;
	ZeroMemory( &light, sizeof(D3DLIGHT9) );
	light.Type       = D3DLIGHT_DIRECTIONAL;
	light.Diffuse.r  = 1.0f;
	light.Diffuse.g  = 1.0f;
	light.Diffuse.b  = 0.9f;
	light.Diffuse.a  = 1.0f;
	light.Range      = 1000.0f;

	// Direction for our light - it must be normalized - pointing down and along z
	D3DXVECTOR3 vecDir;
	vecDir = D3DXVECTOR3(1.5f,-3.0f,0.0f);
	D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecDir );

	// Turn light on
	pd3dDevice->SetLight( 0, &light );
	pd3dDevice->LightEnable( 0, TRUE );

	// Light number 2:
	light.Diffuse.r  = 0.0f;
	light.Diffuse.g  = 0.0f;
	light.Diffuse.b  = 0.5f;
	// Direction for our light - it must be normalized - pointing down and along z
	vecDir = D3DXVECTOR3(-0.0f,0.0f,-0.5f);
	D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecDir );
	// Turn light on
	pd3dDevice->SetLight( 1, &light );
	pd3dDevice->LightEnable( 1, TRUE );

	pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );

	return hr;
}

void Level::OnLostDevice( ) {
	// Device is gone:
	pd3dDevice = NULL;
}

void Level::Render3D(double fTime, float fElapsedTime) {

	// Sanity check:
	assert(pd3dDevice);

	// Update the camera:
	UpdateCameraHardware( );
	
	// Set up the view matrix:
	D3DXMATRIX view;
	gCamera->CalculateViewMatrix(&view);
	pd3dDevice->SetTransform( D3DTS_VIEW, &view );
	
	// Create the world matrix
	D3DXMATRIX matRotX,matRotY,matRotZ;
	
	D3DXMatrixRotationX( &matRotX, gModelRotations.x );
	D3DXMatrixRotationY( &matRotY, gModelRotations.y );
	D3DXMatrixRotationZ( &matRotZ, gModelRotations.z );

	D3DXMATRIX matWorld=matRotX*matRotY*matRotZ;
	
	pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

	// Render the level's 3d:
	pawns.Render3D( pd3dDevice, fTime, fElapsedTime );
}

void Level::Render2D( double fTime, float fElapsedTime ) {

	// If we're paused, tell the player:
	if (mLevelState == LS_PregamePause) {
		font.Render2D( L"Press SPACE to begin", PAUSE_POSITION, 
			WHITE, D3DXVECTOR2(1,1), true );
	}
}


bool Level::TileIsEmpty( int x, int y ) {
	// Sanity check:
	if (   x < 0 || y < 0
		|| x >= iLevelWidth || y >= iLevelHeight )
		return false;

	// First check to see if there's a crate in the tile:
	using namespace std;
	list<Pawn*>::iterator i, end = crates.end( );
	for (i = crates.begin(); i != end; ++i) {
		if (   x == static_cast<int> ((*i)->x)
			&& y == static_cast<int> ((*i)->y) )
			// If there's a crate, return non empty:
			return false;
	}

	// Returns true if the given x,y tile is empty (i.e. non-wall)
	if ( level_def[iCurrLevel][y][x] != 'X'
		&& level_def[iCurrLevel][y][x] != 'L' )
		return true;

	// Otherwise returns false:
	return false;
}

bool Level::CanPushCrate( int x, int y, Pawn* pawn ) {
	// Returns true if pawn can push a crate in (x,y)

	Pawn *crate = NULL;

	using namespace std;
	list<Pawn*>::iterator i, end = crates.end( );
	for (i = crates.begin(); i != end; ++i) {
		if (   x == static_cast<int> ((*i)->x)
			&& y == static_cast<int> ((*i)->y) ) {
			// If there's a crate, return it:
			crate = *i;
			break;
		}
	}

	// No crate here?  Can't push then!
	if (!crate)
		return false;

	// We've got a crate.  Check for space on the other side:
	int dx = x - static_cast<int>(pawn->x), 
		dy = y - static_cast<int>(pawn->y);

	int check_x = x + dx, check_y = y + dy;

	// Look in the square for a wall or tile.  If wall, can't push:
	if (   level_def[iCurrLevel][check_y][check_x] == TILE_WALL
		|| level_def[iCurrLevel][check_y][check_x] == TILE_LIGHT )
		// Found a wall - can't push:
		return false;

	// No wall on other side.  Check for a crate:
	end = crates.end( );
	for (i = crates.begin( ); i != end; ++i ) {
		if (   check_x == static_cast<int> ((*i)->x)
			&& check_y == static_cast<int> ((*i)->y) )
			// If there's a crate, can't push:
			return false;
	}

	// We're good to go!  Return 'true', meaning 'can push!':
	return true;

}

Pawn *Level::GetCrate( int x, int y ) {
	// Returns a crate if found at (x,y) or NULL

	using namespace std;
	list<Pawn*>::iterator i, end = crates.end( );
	for (i = crates.begin(); i != end; ++i) {
		if (   x == static_cast<int> ((*i)->x)
			&& y == static_cast<int> ((*i)->y) )
			// If there's a crate, return it:
			return *i;
	}

	// No crate so return NULL:
	return NULL;
}


void Level::CheckLevelClear( ) {
	// Checks to see if all target zones have a crate on them.
	int targets_accounted_for = 0;

	// For each crate, check if it's on a target.  If so, add one to
	// the accounted_for counter:
	using namespace std;
	list<Pawn*>::iterator i, end = crates.end( );
	for (i = crates.begin(); i != end; ++i) {
		int x = static_cast<int>((*i)->x),
			y = static_cast<int>((*i)->y);
		if ( level_def[iCurrLevel][y][x] == TILE_TARGET )
			++targets_accounted_for;
	}

	// If we've accounted for enough targets, next level:
	if (targets_accounted_for >= iNumTargets) {
		bNewLevel = true;
		bPause = true;
	}
	
}

void Level::RegisterMovement( Pawn *pawn ) {
// Adds a turn to the turn list and clears all later turns.

	// Ensure there's a pawn:
	assert(pawn);
	
	// Clear all turns later than the current turn counter:
	std::list<Turn>::iterator iter;
	for (iter = undoList.begin( ); iter != undoList.end( ); ++iter) {
		if ( iter->turnNo > Registrar::iNumTurns ) {
			undoList.remove( *iter );
			iter = undoList.begin( );
		// clear turns at the current counter if pawn matches:
		} else if (iter->turnNo == Registrar::iNumTurns 
				&& pawn==iter->thePawn ) {
			undoList.remove( *iter );
			iter = undoList.begin( );
		}
	}

	// Add the turn:
	Turn current_turn;
	current_turn.thePawn = pawn;
	current_turn.turnNo = Registrar::iNumTurns;
	current_turn.pushNo = Registrar::iNumPushes;
	current_turn.location.x = pawn->x;
	current_turn.location.y = pawn->y;
	current_turn.location.z = pawn->z;

	undoList.push_back( current_turn );
}

void Level::SetPlayerMoving( bool b ) {
	// Sets whether the player is moving
	bPlayerMoving = b;
}


int operator ==( const Turn &lhs, const Turn &rhs )
{
	return (lhs.thePawn == rhs.thePawn)
		&& (lhs.turnNo == rhs.turnNo)
		&& (lhs.pushNo == rhs.pushNo)
		&& (lhs.location == rhs.location);
}

bool Level::MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {

	// If we press the Undo or Redo keys, update the game state:
	if ( uMsg == WM_KEYDOWN ) {

		int last_listed_turn = FindLastPlayerTurn( ).turnNo;

		switch (wParam) {
			case VK_PRIOR:
				// Page up: We go one event previous:
				if (--Registrar::iNumTurns < 0)
					Registrar::iNumTurns = 0;
				PositionPawnsForCurrentTurn( );
				return true;
			case VK_NEXT: 
				// Page down: We go one event later:
				if (++Registrar::iNumTurns > last_listed_turn)
					Registrar::iNumTurns = last_listed_turn;
				PositionPawnsForCurrentTurn( );
				return true;
			case VK_HOME: 
				// Home: We go to the initial level position:
				Registrar::iNumTurns = 0;
				PositionPawnsForCurrentTurn( );
				return true;
			case VK_END:  
				// End: We go to the end of the undo list:
				Registrar::iNumTurns = last_listed_turn;
				PositionPawnsForCurrentTurn( );
				return true; 
			default:
				return false;
		}
	}

	return false;
}

void Level::ForcePlayer( const D3DXVECTOR3 &location ) {
	// Sanity check:
	assert(player);

	// Force the player into a given position and reset AI.
	player->x = location.x;
	player->y = location.y;
	player->z = location.z;
	player->ResetAi( );
}

void Level::PositionPawnsForCurrentTurn( ) {
	// This function finds a matching registered locations and 
	// positions the pawns in the right spot for the current turn.

	// Iterate through the list:
	std::list<Turn>::iterator iter, end = undoList.end( );
	for ( iter = undoList.begin( ); iter != end; ++iter ) {

		// We've found a turn that is less than the current time
		// and so therefore might be meaningful to us:
		if (iter->turnNo <= Registrar::iNumTurns) {

			// Does it match the player?
			if ( iter->thePawn == player )
				// Move the player to the right spot:
				ForcePlayer( iter->location );

			// Otherwise, it's a simple crate so just move the pawn:
			else {
				iter->thePawn->x = iter->location.x;
				iter->thePawn->y = iter->location.y;
				iter->thePawn->z = iter->location.z;
			}

			// Either way, we're needing to update the push counter:
			Registrar::iNumPushes = iter->pushNo;

		}

	}
}

const Turn &Level::FindLastPlayerTurn( ) {
	Turn *last_player_turn = NULL;
	// Search for a player-matching turn:
	std::list<Turn>::iterator iter, end = undoList.end( );
	for ( iter = undoList.begin( ); iter != end; ++iter ) {
		if ( iter->thePawn == player )
			last_player_turn = &*iter;
	}

	// There should *always* be a player turn:
	assert(last_player_turn);
	
	return *last_player_turn;
}
